home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
SGI Developer Toolbox 6.1
/
SGI Developer Toolbox 6.1 - Disc 4.iso
/
hardware
/
dat
/
dat.c
< prev
next >
Wrap
C/C++ Source or Header
|
1994-08-01
|
49KB
|
1,886 lines
/*
* Copyright (C) 1994, Silicon Graphics, Inc.
* All Rights Reserved.
*
* This is UNPUBLISHED PROPRIETARY SOURCE CODE of Silicon Graphics, Inc.;
* the contents of this file may not be disclosed to third parties, copied or
* duplicated in any form, in whole or in part, without the prior written
* permission of Silicon Graphics, Inc.
*
* RESTRICTED RIGHTS LEGEND:
* Use, duplication or disclosure by the Government is subject to restrictions
* as set forth in subdivision (c)(1)(ii) of the Rights in Technical Data
* and Computer Software clause at DFARS 252.227-7013, and/or in similar or
* successor clauses in the FAR, DOD or NASA FAR Supplement. Unpublished -
* rights reserved under the Copyright Laws of the United States.
*/
/*****************************************************************************
* dat.c: A simple DAT Tape Driver
*
* Introduction:
* -------------
* This is a User-Level DAT device driver for Indigo-2.
* The purpose of this driver is to provide examples as how to
* use 'dslib.a' routines to drive any Scsci device.
* The code in this file shows you how to:
*
* - Load and Unload the tape
* - find out the tape format (single or double part).
* - find out tape capacity.
* - partition a tape
* - switch partitions on the tape
* - change default tape block
* - Read and Write from/to the tape
* - move forward and backward
* ... and many more
*
* All above functions are provided using 'dslib' routines.
* To provide these examples, we had to come up with a DAT tape
* format and Directory structure. We use the tape in Double
* Partition mode: Partition 1 (the first physical partition of
* the tape) is used for the Tape Control block and Directory entries.
* Partition 0 (the rest of the tape) is used for Data.
*
* Partition 1: Control Block and Directoriy:
* ------------------------------------------
* Tape COntrol Block is used to keep track of data about the whole
* tape in general (see tpCb_t). This data is written in Block 0 of
* the Partition 0. The rest of the partition (block 1 onward) is
* used to store the directory entries. Currently, we use only one
* block for the directory for simplicity's sake. You can easily change
* this restriction. So, the format of partition one is:
*
* +----------------+
* | Control Block | block 0
* +----------------+
* | Directory | block 1
* +----------------+
* | Unused | block 2
* .................. ...
* | Unused | block N
* +----------------+
*
* Partition 0: Data files:
* -------------------------
* The files themselves are stored in Partition 0. For each
* Directory entry in Partition 1, there is a corresponding file
* in Partition 0. The files are separated from each other by a
* File Mark after their last block. We use these File_Marks to
* move around in the tape and access a particular file.
* The format of Partition 0 is:
*
* +-------------------+
* | File 0 | block 0
* +-------------------+
* | File 0 | block 1
* ..................... ...
* | File 0 | block n
* +===================+ <--------- File Mark
* ....
* +===================+
* | File N |
* +-------------------+
* | File N |
* .....................
* | File N |
* +===================+ <--------- File Mark
*
* Technical References:
* ---------------------
* Python DDS and DDS-DC Dat Tape Drives, Scsi Manual
* IRIX Device Driver Programmer's Guide, Scsi User-Level Device Drivers.
*
*****************************************************************************/
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <memory.h>
#include <sys/stat.h>
#include <time.h>
#include <dslib.h>
/*
* tpCb_t: Tape Control Block
*
* This struct contains data regarding the whole tape and
* is stored in Partition 1, block 0. We keep track of the
* following data in this struct:
*
* - cb_dtot: Total directory entries in this tape.
* - cb_time: The last Date/time this tape was written to.
* - cb_remsize: Remaining tape capacity in K Bytes.
* - cb_maxsize: Maximum tape capacity in K Bytes.
* - cb_id: Tape ID to recognize the tape as SGI tape.
* - cb_name: Tape name.
*/
typedef struct tpCb_s {
int cb_dtot; /* total Directory entries */
long cb_time; /* last date/time tape was written to */
int cb_remsize; /* remainig K Bytes on tape (part-0) */
int cb_maxsize; /* Max size of the tape (part 0, K Bytes) */
uchar_t cb_id[80]; /* tape ID - always set to SGI_TAPE */
uchar_t cb_name[80];/* tape Name/Description */
} tpCb_t;
/* cb_id value */
#define SGI_TAPE "SGI TAPE VERSION 1.0"
/* ================================
* tpDir_t: Tape Directory Entry
* ================================
*
* There is one Directory entry for each file on tape.
* The directory of the tape is stored in Partition 1, Block 1
* afterwards. The data we keep for each file is:
*
* - d_fno: File number
* - d_len: File Length in bytes.
* - d_time: Creation date of the file on tape.
* - d_type: File type (always TYPE_FILE for now).
* - d_name: File name
*/
#define TP_NAME_LEN 25
typedef struct tpDir_s {
int d_fno; /* file Number */
int d_len; /* file length (bytes) */
long d_time; /* date/time of creation */
uchar_t d_type; /* file type */
char d_name[TP_NAME_LEN]; /* file name */
} tpDir_t;
/* d_type values - can be added for custom files */
#define TYPE_FILE 1
/* =========================
* tpStat_t: Tape Status
* =========================
*
* This struct is used to keep track of current running info.
* This is run-time based and is never written to the tape.
* The data we keep track of is as follow:
*
* - st_state: State of the tape
* - st_fno: Current Tape position (which file).
* - st_part: Current Tape Partition (DIR or DATA)
* - st_dno: Last Directory entry shown
*/
typedef struct tpStat_s {
int st_state; /* i.e: tape opened, written to, etc */
int st_fno; /* current file position (file number) */
int st_part; /* current Partition on tape */
int st_dno; /* Last directory shown */
} tpStat_t;
/* bits in st_state */
#define TAPE_LOADED 0x01
#define TAPE_WRITTEN 0x02
#define TAPE_WRITE_PROTECT 0x04
#define TAPE_DOUBLE_PART 0x08
/* st_part values */
#define DIR_PARTITION 1
#define DATA_PARTITION 0
/* The program commands supported */
#define CMD_HELP "help"
#define CMD_INIT "init"
#define CMD_OPEN "open"
#define CMD_DIR "dir"
#define CMD_DIRU "diru"
#define CMD_DIRD "dird"
#define CMD_WRITE "write"
#define CMD_READ "read"
#define CMD_CLOSE "close"
#define CMD_EXIT "exit"
/* Scsi tape commands */
#define WRITE_FM 0x10
#define WRITE 0x0a
#define READ 0x08
#define REWIND 0x01
#define LOAD_UNLOAD 0x1B
#define SPACE 0x11
#define MODE_SENSE 0x1A
#define MODE_SELECT 0x15
#define LOG_SENSE 0x4D
/* Scsi commands Pages and various bits */
#define MED_PART_PAGE 0x11
#define DEVICE_CONFIG_PAGE 0x10
#define TAPE_CAPACITY_PAGE 0x31
#define SPACE_FMARKS 0x01
#define DBD_BIT 0x08
#define IDP_BIT 0x20
#define PC_BIT 0x40
#define PF_BIT 0x10
#define CAP_BIT 0x40
#define LOAD_BIT 0x01
#define RSMK_BIT 0x20;
#define UNLOAD_BIT 0x00
#define PSUM_MBYTES 0x10
#define PSUM_KBYTES 0x08
#define PSUM_BYTES 0x00
/* Misc. Defines */
#define TAPE_BLOCK_SIZE 1024
#define MAX_DIR (TAPE_BLOCK_SIZE / sizeof(tpDir_t) )
#define MAX_BUSY_RETRY 3
#define FORWARD 1
#define BACKWARD 0
#define FOREVER for(;;)
#define DIR_PARTITION_SIZE 1 /* 1 Mbytes for Directory partition */
#define SCREEN_SIZE 30 /* Screen size for showing dir */
/*
* some SCSI commands takes longer to be executed.
* These are time out values in seconds for those commands.
* ( 300 = 5 Minutes, 600 = 10 minutes )
*/
#define TIMER_READ_WRITE 120*1000 /* 1 minutes */
#define TIMER_LOAD_UNLOAD 300*1000 /* 5 minutes */
#define TIMER_SWITCH_PARTITION 300*1000 /* 5 minutes */
#define TIMER_REWIND 300*1000 /* 5 minutes */
#define TIMER_FILE_MARKS 300*1000 /* 5 minutes */
#define TIMER_DOUBLE_PARTIION 900*1000 /* 15 minutes */
#define seq(s1,s2) (strcmp(s1,s2) == 0)
/* Global variables */
tpStat_t tpStat;
dsreq_t *tpDsreq;
uchar_t tpBlock[TAPE_BLOCK_SIZE];
char *tpdev;
tpCb_t tpCb;
tpDir_t tpDir[MAX_DIR];
/****************************************************************************
* *
* M a i n P r o g r a m *
* *
****************************************************************************/
main (int argc, char **argv)
{
register int flags;
char tpCmd[80];
if ( argc <= 1 ) {
printf ("dat Usage: dat </dev/<scsi DAT tape>\n");
exit();
}
tpdev = argv[1];
if ( (tpDsreq = dsopen(tpdev, O_RDWR)) == (dsreq_t *)NULL ) {
printf ("dat: Error opening DAT tape, errno = %d\n", errno );
exit();
}
/* dsdebug |= (1 | 2); */
memset ( &tpStat, 0, sizeof(tpStat_t) );
FOREVER {
printf ("\n\ndat> ");
printf ("dat> Commands: init, open, close, read, write, exit\n");
printf ("dat> dir, diru (Dir Up), dird (Dir down)\n");
printf ("\ndat> ");
gets (tpCmd);
/* Help command */
if ( seq(tpCmd, CMD_HELP) ) {
showHelp();
continue;
}
/* Initialize command */
if ( seq(tpCmd, CMD_INIT) ) {
initTape();
continue;
}
/* Open Command */
if ( seq(tpCmd, CMD_OPEN) ) {
openTape();
continue;
}
/* Show Directory */
if ( seq(tpCmd, CMD_DIR) ) {
showDir(0);
continue;
}
/* Directory Up */
if ( seq(tpCmd, CMD_DIRU) ) {
showDir(tpStat.st_dno - 2 * SCREEN_SIZE);
continue;
}
/* Directory Down */
if ( seq(tpCmd, CMD_DIRD) ) {
showDir(tpStat.st_dno);
continue;
}
/* Write Command */
if ( seq(tpCmd, CMD_WRITE) ) {
writeFile();
continue;
}
/* Read Command */
if ( seq(tpCmd, CMD_READ) ) {
readFile();
continue;
}
/* Close Tape */
if ( seq(tpCmd, CMD_CLOSE) ) {
closeTape();
continue;
}
if ( seq(tpCmd, CMD_EXIT) ) {
cleanUp();
}
printf ("Unknown command ..try help.");
}
}
/*************************************************************************
* i n i t T a p e
*************************************************************************
*
* Name: initTape
*
* Purpose: Initializes a tape.
*
* Returns: 0 = Success, -1 = Failure.
*
*************************************************************************/
initTape()
{
register tpCb_t *pcb;
tpCb_t tpcb;
int part_size, max_size, rem_size;
if ( tpStat.st_state & TAPE_WRITE_PROTECT ) {
printf ("dat> Tape is Write Protected ..cannot Initialize\n");
return(-1);
}
pcb = &tpcb;
memset ( pcb, 0, sizeof(tpCb_t) );
printf ("dat> Enter tape name below <Return> to exit:\n");
printf ("dat> Name: ");
gets ( pcb->cb_name );
if ( ( pcb->cb_name[0] == '\n') || ( pcb->cb_name[0] == '\0') )
return(0);
sprintf ( pcb->cb_id, "%s", SGI_TAPE);
/*
* Tape is not loaded yet.
*/
if ( !(tpStat.st_state & TAPE_LOADED) ) {
/* Do a Test Unit Ready first */
if ( testUnitReady() == -1 ) {
printf ("dat> Tape is not ready !\n");
return(-1);
}
if ( getTapeFormat(&part_size) == -1 )
return(-1);
/*
* If tape is not partitioned or it is partitioned but
* Dir partition is more than our specs, repartition it.
*/
if ( part_size != DIR_PARTITION_SIZE ) {
if ( initDoublePart() == -1 )
return(-1);
}
/* tape is already double_partitioned, just load it */
else {
if ( loadTape() == -1 )
return(-1);
}
}
if ( switchPart(DIR_PARTITION) == -1 )
return(-1);
/* get tape capacity */
if ( getTapeSize(&max_size, &rem_size) == -1 )
return(-1);
pcb->cb_remsize = pcb->cb_maxsize = max_size;
/* so far so good ..initialize tape Control Block */
printf ("dat>\t ...initializing tape Control Block.\n");
memset (tpBlock, 0, TAPE_BLOCK_SIZE);
if ( time ( &pcb->cb_time ) == -1 )
printf ("dat> Couldn't get date/time ..errno = %d\n", errno);
memcpy ( &tpCb, pcb, sizeof(tpCb_t) );
memcpy ( tpBlock, pcb, sizeof(tpCb_t) );
if ( writeBlock() == -1 )
return(-1);
/* Initialize tape Directories */
memset (tpBlock, 0, TAPE_BLOCK_SIZE);
printf ("dat>\t ...initializing Tape Directory.\n");
if ( writeBlock() == -1 )
return(-1);
memset ( &tpDir, 0, sizeof(tpDir) );
if ( switchPart(DATA_PARTITION) == -1 )
return(-1);
tpStat.st_state |= TAPE_LOADED;
tpStat.st_state &= ~TAPE_WRITTEN;
tpStat.st_fno = 0;
printf ("dat>\t ...Tape is initialized\n");
return(0);
}
/*************************************************************************
* o p e n T a p e
*************************************************************************
*
* Name: openTape
*
* Purpose: Opens the tape, reads in tape control block and
* Directories. If everything goes well, we switch to
* Data partition (partition 0).
*
* Returns: 0 = Success, -1 = Error
*
*************************************************************************/
openTape()
{
register tpCb_t *tpcb;
register tpStat_t *tpst;
int part_size;
if ( tpStat.st_state & TAPE_LOADED ) {
printf ("dat> Tape is already opened\n");
return(-1);
}
tpst = &tpStat;
tpcb = &tpCb;
memset (tpst, 0, sizeof(tpStat_t) );
if ( testUnitReady() == -1 ) {
printf ("dat> Tape is not ready !\n");
return(-1);
}
/*
* first see if tape is partitioned correctly
*/
if ( getTapeFormat(&part_size) == -1 )
return(-1);
if ( part_size != DIR_PARTITION_SIZE ) {
printf ("dat> Foreign tape detected ..not an SGI tape\n");
unloadTape();
return(-1);
}
/* Load the tape */
if ( loadTape() == -1 )
return(-1);
if ( switchPart(DIR_PARTITION) == -1 )
return(-1);
/* We are in Dir Partition ..read tape Control Block */
printf ("dat>\t ...reading Tape Control Block\n");
if ( readBlock() == -1 )
return(-1);
memcpy ( tpcb, DATABUF(tpDsreq), sizeof(tpCb_t) );
if ( !seq(tpcb->cb_id, SGI_TAPE) ) {
printf ("dat> This is not a SGI formatted tape\n");
unloadTape();
return(-1);
}
printf ("dat>\t ...tape name = %s, total files = %d\n",
tpcb->cb_name, tpcb->cb_dtot);
/* Read tape Directory */
if ( tpcb->cb_dtot > 0 ) {
printf ("dat>\t ...reading the tape directory\n");
if ( readBlock() == -1 )
return(-1);
memcpy ( &tpDir[0], DATABUF(tpDsreq),
tpcb->cb_dtot * sizeof(tpDir_t) );
}
if ( switchPart(DATA_PARTITION) == -1 )
return(-1);
tpst->st_fno = 0;
printf ("dat>\t ...tape is ready\n");
return(0);
}
/*************************************************************************
* w r i t e F i l e
*************************************************************************
*
* Name: writeFile
*
* Purpose: Writes a given file from Disk to Tape
*
* Returns: 0 = Success, -1 = Error
*
*************************************************************************/
writeFile()
{
register int i, totbytes, totblocks, bytes, fd;
register tpStat_t *tps;
register tpCb_t *tpc;
register tpDir_t *tpd;
uchar_t fn[80], fpath[80];
struct stat st;
tps = &tpStat;
tpc = &tpCb;
/* is there any tape at all ? */
if ( !(tps->st_state & TAPE_LOADED) ) {
printf ("dat> No tape is opened yet\n");
return(-1);
}
if ( tps->st_state & TAPE_WRITE_PROTECT ) {
printf ("dat> Tape is Write Protected\n");
return(-1);
}
/* Get the file path and name */
printf ("dat> Below enter the full path name to the file:\n");
printf ("dat> File: ");
gets (fpath);
if ( (fpath[0] == '\n') || (fpath[0] == '\n') )
return(0);
/* see if this file is just a plain ordinary file */
if ( stat(fpath, &st) == -1 ) {
printf ("dat> Cannot get stat of the file, errno = %d\n",
errno);
return(-1);
}
/* is it a regular file ? (see sys/stat.h) */
if ( !(S_ISREG(st.st_mode)) ) {
printf ("dat> This is not a regular file\n");
return(-1);
}
/* calculate total tape blocks needed */
totblocks = 1;
totbytes = st.st_size;
if ( totbytes > TAPE_BLOCK_SIZE ) {
totblocks = totbytes / TAPE_BLOCK_SIZE;
if ( totbytes % TAPE_BLOCK_SIZE > 0 )
totblocks++;
}
/* do we have enough room ? */
if ( tpc->cb_remsize <= totblocks ) {
printf ("dat> Not enough room on tape ..rem = %d, needed = %d\n",
tpc->cb_remsize, totblocks );
return(-1);
}
printf ("dat>\t ...Writing the file as File No: %d\n", tpc->cb_dtot);
printf ("dat>\t ...currently at file no: %d\n", tps->st_fno );
/*
* We are not at the end of the tape ..move to the end of tape.
* Note: Since each file's last position is marked with a
* File Mark, we move <currnt_file - total_file> File Mark
* forward in tape. The tape position will be at the end of
* the last File Mark detected, which places the tape position
* on the beginning of the block we want to write.
*/
if ( tps->st_fno != tpc->cb_dtot ) {
printf ("dat> Moving to End of Tape\n");
if ( moveFm ( FORWARD, tpc->cb_dtot - tps->st_fno) == -1 )
return(-1);
}
/* We have absolutly no excuse now ! Get the file name */
for ( i = strlen(fpath) - 1; i>= 0; i-- ) {
if ( fpath[i] == '/' ) {
sprintf (fn, &fpath[i+1]);
break;
}
if ( i == 0 ) {
sprintf (fn, fpath);
break;
}
}
/* open the file name */
if ( (fd = open(fpath, O_RDONLY|O_NONBLOCK)) <= 0 ) {
printf ("dat> Error opening file, errno = %d\n", errno);
return(-1);
}
/*
* Read and write to the tape
*/
printf ("dat>\t ...Writing %d bytes (total of %d tape blocks)\n",
totbytes, totblocks );
while ( totbytes > 0 ) {
bytes = (totbytes > TAPE_BLOCK_SIZE ? TAPE_BLOCK_SIZE:totbytes);
/* if reading less than block size, zero out the block */
if ( bytes < TAPE_BLOCK_SIZE )
memset (tpBlock, 0, TAPE_BLOCK_SIZE);
if ( (bytes = read (fd, tpBlock, bytes)) < 0 ) {
printf ("dat> Read error, errno = %d\n", errno);
return(-1);
}
totbytes -= bytes;
if ( writeBlock() == -1 ) {
printf ("dat> Error writing to tape\n");
return(-1);
}
}
close (fd);
if ( writeFm() == -1 ) {
printf ("dat> Could not write End_of_File Mark\n");
return(-1);
}
tpc->cb_remsize -= totblocks;
/* create a directory entry for this file */
tpd = &tpDir[tpc->cb_dtot];
sprintf (tpd->d_name, fn);
tpd->d_fno = tpc->cb_dtot++;
tpd->d_len = st.st_size;
tpd->d_type = TYPE_FILE;
time( &tpd->d_time );
printf ("dat>\t ...File written as File No: %d\n", tpd->d_fno );
printf ("dat>\t ...Total files on tape: %d, currently at file no: %d\n",
tpc->cb_dtot, tps->st_fno );
return(0);
}
/*************************************************************************
* r e a d F i l e
*************************************************************************
*
* Name: readFile
*
* Purpose: Read a file from tape to disk.
*
* Returns: 0 = Success, -1 = Error
*
*************************************************************************/
readFile()
{
register int i, totbytes, totblocks, bytes;
register tpStat_t *tps;
register tpCb_t *tpc;
register tpDir_t *tpd;
register int fd;
int fno;
uchar_t fpath[80];
tps = &tpStat;
tpc = &tpCb;
/* is there any tape at all ? */
if ( !(tps->st_state & TAPE_LOADED ) ) {
printf ("dat> No tape is opened yet\n");
return(-1);
}
/* Get the file number on tape */
FOREVER {
printf ("dat> File number to copy <-1 = exit>: ");
scanf ("%i", &fno);
if ( fno == -1 )
return(0);
if ( fno >= tpc->cb_dtot ) {
printf ("dat> No such file number ..try again\n");
continue;
}
break;
}
gets(fpath); /* get rid of CR left over */
/*
* show info for this file and get the path to the
* disk file name to create.
*/
tpd = &tpDir[fno];
printf ("dat>\t ...File no: %d, Name: %s, Size: %d\n",
tpd->d_fno, tpd->d_name, tpd->d_len );
printf ("dat> Below enter the full path name to store the file\n");
printf ("dat> Name: ");
gets (fpath);
if ( (fpath[0] == '\n') || (fpath[0] == '\n') )
return(0);
/* Open the file - the old content will be lost */
if ( (fd = creat(fpath, S_IREAD | S_IWRITE)) < 0 ) {
printf ("dat> Cannot create/open file, errno = %d\n", errno);
return(-1);
}
printf ("dat>\t ...current file position: %d, requested file: %d\n",
tps->st_fno, fno );
/*
* if requested file is 0, simply rewind the tape
*/
if ( fno == 0 ) {
if ( rewindTape() == -1 )
return(-1);
}
/*
* File is other that file 0 and we are at the requested file.
* We may be in middle or the last block of the file.
* We should get back to block 0 of the file. So, we should
* skip one File_Mark backward and then one File_Marl Forward.
*/
else {
if ( fno == tps->st_fno ) {
if ( moveFm(BACKWARD, 1) == -1 )
return(-1);
if ( moveFm(FORWARD, 1) == -1 )
return(-1);
}
/* we are not at the requested file number */
else {
/*
* Move the tape to the requested file.
* Depending on where we are in relation to the file, we
* move File_Marks forward or backward. When moving backward,
* we have to move Forward one File_Mark to be at the block
* 0 of the file.
*/
if ( tps->st_fno < fno ) {
if ( moveFm (FORWARD, fno - tps->st_fno ) == -1 )
return(-1);
}
else {
if ( moveFm (BACKWARD, (tps->st_fno - fno)+1 ) == -1 )
return(-1);
if ( moveFm (FORWARD, 1) == -1 )
return(-1);
}
}
}
totbytes = tpd->d_len;
/* calculate tape blocks to read. */
totblocks = 1;
if ( totbytes > TAPE_BLOCK_SIZE ) {
totblocks = totbytes / TAPE_BLOCK_SIZE;
if ( totbytes % TAPE_BLOCK_SIZE > 0 )
totblocks++;
}
printf ("dat>\t ...Reading total of %d bytes [ %d blocks ]\n",
totbytes, totblocks );
while ( totblocks > 0 ) {
if ( readBlock() == -1 ) {
printf ("dat> Error reading from tape\n");
close (fd);
return(-1);
}
bytes = (totbytes > TAPE_BLOCK_SIZE ? TAPE_BLOCK_SIZE:totbytes);
if ( (bytes = write (fd, tpBlock, bytes)) <= 0 ) {
printf ("dat> Error writing to file, errno = %d\n",
errno );
close (fd);
return(-1);
}
totbytes -= bytes;
totblocks--;
}
close (fd);
printf ("dat>\t ...File successfully written to disk\n");
return(0);
}
/*************************************************************************
* c l o s e T a p e
*************************************************************************
*
* Name: closeTape
*
* Purpose: Closes the tape. It updates the directory if needed.
*
* Returns: 0 = Success, -1 = Error
*
*************************************************************************/
closeTape()
{
register tpStat_t *tps;
register tpCb_t *tpc;
tps = &tpStat;
tpc = &tpCb;
/* one or more files were written to tape ..update directory */
if ( tps->st_state & TAPE_WRITTEN ) {
printf ("dat>\t ...Updating Directory\n");
if ( switchPart(DIR_PARTITION) == -1 )
return(-1);
/* write tape Control Block */
printf ("dat>\t ...Writing Tape Control Block\n");
memset (tpBlock, 0, TAPE_BLOCK_SIZE );
time (&tpc->cb_time);
memcpy (tpBlock, tpc, sizeof(tpCb_t) );
if ( writeBlock() == -1 )
return(-1);
/* write tape Directory back to tape */
printf ("dat>\t ...Writing Tape Directory\n");
memset (tpBlock, 0, TAPE_BLOCK_SIZE);
memcpy (tpBlock, &tpDir[0], tpc->cb_dtot * sizeof(tpDir_t) );
if ( writeBlock() == -1 )
return(-1);
}
unloadTape();
tps->st_state &= ~TAPE_WRITTEN;
tps->st_state &= ~TAPE_LOADED;
return(0);
}
/*************************************************************************
* m o v e F m
*************************************************************************
*
* Name: moveFm
*
* Purpose: Moves given number of File Marks Forward or Backward.
* The important thing to remember here is that when
* moving forward, tape will be positioned at the End of
* the last File_Mark (first block of the next file). When
* moving backward, tape will be positioned at the Beginning
* of the last File_Mark detected (the last block of the prev.
* file).
*
* Returns: 0 = Success, -1 = Error
*
*************************************************************************/
moveFm (int what, int fmNo)
{
uchar_t b1, b2, b3, b4;
ulong old_timer;
register int err;
printf ("dat>\t ...Moving the tape %d File_Marks %s, current pos: %d\n",
fmNo, (what == FORWARD ? "Forward":"Backward"), tpStat.st_fno);
fmNo = (what == FORWARD ? fmNo : fmNo * -1);
/* create a Space Cdb */
b1 = 0;
b1 |= SPACE_FMARKS;
b2 = fmNo >> 16;
b3 = fmNo >> 8;
b4 = fmNo;
fillg0cmd (tpDsreq, CMDBUF(tpDsreq), SPACE, b1, b2, b3, b4, 0 );
filldsreq (tpDsreq, 0, 0, DSRQ_READ | DSRQ_SENSE);
old_timer = TIME(tpDsreq);
TIME(tpDsreq) = TIMER_FILE_MARKS;
err = issueCmd();
TIME(tpDsreq) = old_timer; /* put back the timer */
if ( err == -1 ) {
printf ("dat> Could not position tape\n");
return(-1);
}
/* update our position */
tpStat.st_fno += fmNo;
printf ("dat>\t ...new file position = %d\n", tpStat.st_fno);
return(0);
}
/*************************************************************************
* t e s t U n i t R e a d y
*************************************************************************
*
* Name: testUnitReady
*
* Purpose: Issues Test_Unit_Ready.
*
* Returns: 0 = Success (tape is ready), -1= Error
*
*************************************************************************/
testUnitReady()
{
register int i, st, rt;
printf ("dat>\t ...Issuing a Test Unit Ready\n");
for ( i = 0; i < 3; i++ ) {
errno = 0;
if (testunitready00(tpDsreq) == -1) {
printf ("dat> Error, RET(dsreq) = %x, errno = %d\n",
RET(tpDsreq), errno);
return(-1);
}
/* check device's status */
st = STATUS(tpDsreq);
switch ( st ) {
case STA_GOOD:
return(0);
case STA_CHECK:
if ( checkSense() == -1 )
return(-1);
break;
default:
break;
}
sleep(1);
}
return(-1);
}
/*************************************************************************
* g e t T a p e S i z e
*************************************************************************
*
* Name: getTapeSize
*
* Purpose: Get tape size and report Max and Remaining K Bytes for
* partition 0. All sizes are in K Bytes.
*
* Returns: 0 = Success, -1 = Error
*
*************************************************************************/
getTapeSize( int *max_size, int *rem_size )
{
uchar_t b2, b6, b7, b8;
uchar_t *p;
printf ("dat>\t ...Obtainig Tape Capacity values\n");
/*
* Issue a Log_Sense, requesting Tape_Capacity page
*/
b2 = 0;
b2 |= (PC_BIT | TAPE_CAPACITY_PAGE);
b6 = 1;
b7 = TAPE_BLOCK_SIZE >> 8;
b8 = TAPE_BLOCK_SIZE;
fillg1cmd (tpDsreq, CMDBUF(tpDsreq),
LOG_SENSE, 0, b2, 0, 0, 0, b6, b7, b8, 0);
filldsreq (tpDsreq, tpBlock, TAPE_BLOCK_SIZE, DSRQ_READ | DSRQ_SENSE);
if ( issueCmd() == -1 )
return(-1);
/*
* The Tape Capacity data should be in the tape buffer now.
* Bypass the 4-byte header and access the data.
*/
p = DATABUF(tpDsreq);
p += 4;
*rem_size = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
*max_size = (p[8] << 24) | (p[9] << 16) | (p[10] << 8) | p[11];
printf ("dat>\t ...tape size: Max = %d KBytes, Rem = %d KBytes\n",
*max_size, *rem_size );
return(0);
}
/*************************************************************************
* s w i t c h P a r t
*************************************************************************
*
* Name: switchPart
*
* Purpose: switches Partition on tape.
*
* Returns: 0 = Success, -1 = Error
*
*************************************************************************/
switchPart ( part )
{
uchar_t b1, b2, b4;
uchar_t *p;
short err;
ulong old_dstime;
printf ("dat>\t ...Switching to %s Partition, currently in %s Partition\n",
(part == DIR_PARTITION ? "DIR" : "DATA" ),
(tpStat.st_part == DIR_PARTITION ? "DIR" : "DATA") );
/*
* if we are already in the wanted Partition,
* then simply return.
*
if ( tpStat.st_part == part )
return(0);
**/
/*
* Do a Mode_Sense and ask for Device_Config page
* ask for no Block Descriptor data
*/
b1 = 0;
b1 |= DBD_BIT;
b2 = DEVICE_CONFIG_PAGE;
b4 = 100; /* size allocated for Dev_Config page */
fillg0cmd (tpDsreq, CMDBUF(tpDsreq), MODE_SENSE, b1, b2, 0, b4, 0);
filldsreq (tpDsreq, tpBlock, TAPE_BLOCK_SIZE, DSRQ_READ | DSRQ_SENSE);
if ( issueCmd() == -1 )
return(-1);
/*
* Access Device_Config data. Set CAP bit to 1 and
* set Active_Partition to requested value
*/
p = DATABUF(tpDsreq);
/* Parameter List Header */
p[0] = p[1] = p[2] = p[3] = 0;
p[2] |= 0x10;
p += 4; /* p-> Device Config Data */
p[0] = DEVICE_CONFIG_PAGE;
p[2] |= CAP_BIT;
p[3] = part;
p[8] = 0;
p[8] |= RSMK_BIT;
p[10] = 0x10;
p[4] = p[5] = p[9] = p[11] = p[12] = p[13] = p[14] = p[15] = 0;
/*
* Now issue a Mode_Select with whatever we have in tpBlock
*/
b1 = 0;
b1 |= PF_BIT; /* Scisi-2 interpretation */
b4 = 20; /* 4-byte Parameter List + 16-byte Page data */
fillg0cmd (tpDsreq, CMDBUF(tpDsreq), MODE_SELECT, b1, 0, 0, b4, 0);
filldsreq (tpDsreq, tpBlock, TAPE_BLOCK_SIZE, DSRQ_WRITE | DSRQ_SENSE);
/* takes a while */
old_dstime = TIME(tpDsreq);
TIME(tpDsreq) = TIMER_SWITCH_PARTITION;
err = issueCmd();
TIME(tpDsreq) = old_dstime; /* put back the timer */
if ( err == -1 )
return(-1);
tpStat.st_part = part;
return (0);
}
/*************************************************************************
* g e t T a p e F o r m a t
*************************************************************************
*
* Name: getTapeFormat
*
* Purpose: figures out whether this tape is Single Partition or
* Double Partition. Sets the TAPE_DOUBLE_PART bit in
* tpStat.st_state accordingly.
*
* Returns: 0 = Success, -1 = Error
*
*************************************************************************/
getTapeFormat( int *part_size)
{
uchar_t size_unit[10];
uchar_t b1, b2, b4;
uchar_t *p;
uchar_t psum;
printf ("dat>\t ...Figuring out tape format.\n");
/*
* Issue a Mode_Sense and ask for Medioum_Partition Page.
* Note that we ask for no Block Descriptor data.
*/
b1 = 0;
b1 |= DBD_BIT;
b2 = MED_PART_PAGE;
b4 = 100; /* size allocated */
fillg0cmd (tpDsreq, CMDBUF(tpDsreq), MODE_SENSE, b1, b2, 0, b4, 0);
filldsreq (tpDsreq, tpBlock, TAPE_BLOCK_SIZE, DSRQ_READ | DSRQ_SENSE);
if ( issueCmd() == -1 )
return(-1);
/*
* The Medium_Page data should be in tape buffer now.
*/
p = DATABUF(tpDsreq);
p += 4; /* p-> Medium_Page data */
*part_size = 0;
/* if tape is Double_partitioned */
if ( p[3] > 0 ) {
psum = p[4] >> 3;
*part_size = ( (p[8] << 8) | p[9] );
switch ( psum ) {
case 0: sprintf (size_unit, "Bytes"); break;
case 1: sprintf (size_unit, "K Bytes"); break;
case 2: sprintf (size_unit, "M Bytes"); break;
}
printf ("dat>\t ...Tape is partitioned, Partition size = %d %s\n",
*part_size, size_unit );
tpStat.st_state |= TAPE_DOUBLE_PART;
return (0);
}
else printf ("dat>\t ...Tape is Single Partition\n");
tpStat.st_state &= ~TAPE_DOUBLE_PART;
return(0);
}
/*************************************************************************
* i n i t D o u b l e P a r t
*************************************************************************
*
* Name: initDoublePart
*
* Purpose: Initializes the tape as Double Partition.
* We set DIR_PART_SIZE Mega bytes for Directory Partition
* (first Partition on tape) and leave the rest of the
* tape for Files.
*
* Returns: 0 = Success, -1 = Error
*
*************************************************************************/
initDoublePart()
{
uchar_t b1, b2, b4;
short err;
uchar_t *p;
ulong old_dstime;
int part_size;
printf ("dat>\t ...Initializing the tape as a Double Partition\n");
/*
* The tape Medium Partition format is returned to us
* by issuing a Mode_Sense and indication that we want
* Medium _partition Page data. Ask for no Block Descr. data
*/
b1 = 0;
b1 |= DBD_BIT;
b2 = MED_PART_PAGE;
b4 = 100; /* size allocated to receive the data */
fillg0cmd (tpDsreq, CMDBUF(tpDsreq), MODE_SENSE, b1, b2, 0, b4, 0);
filldsreq (tpDsreq, tpBlock, TAPE_BLOCK_SIZE, DSRQ_READ | DSRQ_SENSE);
if ( issueCmd() == -1 )
return(-1);
/* The Medium_Partition data should be available now.
* Set Additional_Partition field and Partition size and set
* IDP bit to 1.
*/
p = DATABUF(tpDsreq);
/* format Parameter List header */
p[0] = p[1] = p[2] = p[3] = 0;
p[2] |= 0x10;
/* set Medium_Page data */
p += 4; /* p-> Medium_Page data */
p[1] = 8;
p[2] = 0;
p[4] = 0;
p[3] = 1; /* Additionla Partition Defined */
p[4] |= (IDP_BIT | PSUM_MBYTES);
p[8] = 0;
p[9] = DIR_PARTITION_SIZE;
b1 = 0;
b1 |= PF_BIT; /* Scisi-2 interpretation */
b4 = 14; /* 4-byte Parameter List + 10-byte Page data */
fillg0cmd (tpDsreq, CMDBUF(tpDsreq), MODE_SELECT, b1, 0, 0, b4, 0);
filldsreq (tpDsreq, tpBlock, TAPE_BLOCK_SIZE, DSRQ_WRITE | DSRQ_SENSE);
/* takes a long time */
old_dstime = TIME(tpDsreq);
TIME(tpDsreq) = TIMER_DOUBLE_PARTIION;
err = issueCmd();
TIME(tpDsreq) = old_dstime; /* put back old value */
if ( err == -1 )
return(-1);
tpStat.st_state |= (TAPE_DOUBLE_PART | TAPE_LOADED);
tpStat.st_fno = 0;
tpStat.st_part = DIR_PARTITION;
return(0);
}
/*************************************************************************
* w r i t e B l o c k
*************************************************************************
*
* Name: writeBlock
*
* Purpose: Writes tape's buffer to current position on tape.
*
* Returns: 0 = Success, -1 = Error
*
*************************************************************************/
writeBlock()
{
uchar_t b4, b1;
short err;
ulong old_timer;
if ( tpStat.st_state & TAPE_WRITE_PROTECT ) {
printf ("dat> Tape is write protected.");
return(-1);
}
b1 = 0x01; /* Fixed size blocks */
b4 = 1; /* write 1 block */
/**
printf ("dat> Writing one block to tape, part = %s\n",
(tpStat.st_part == DIR_PARTITION? "DIR":"DATA") );
**/
fillg0cmd(tpDsreq, CMDBUF(tpDsreq), WRITE, b1, 0, 0, b4, 0);
filldsreq (tpDsreq, tpBlock, TAPE_BLOCK_SIZE, DSRQ_WRITE | DSRQ_SENSE);
old_timer = TIME(tpDsreq);
TIME(tpDsreq) = TIMER_READ_WRITE;
err = issueCmd();
TIME(tpDsreq) = old_timer;
if ( err == 0 )
tpStat.st_state |= TAPE_WRITTEN;
return ( err );
}
/*************************************************************************
* r e a d B l o c k
*************************************************************************
*
* Name: readBlock
*
* Purpose: Reads tpBlock buffer from current position on tape.
*
* Returns: 0 = Success, -1 = Error
*
*************************************************************************/
readBlock()
{
uchar_t b4, b1;
ulong old_timer;
int err;
b1 = 0x01; /* Fixed size blocks */
b4 = 1; /* read 1 block */
/**
printf ("dat>\t ...Reading one block from tape, part = %s\n",
(tpStat.st_part == DIR_PARTITION? "DIR":"DATA") );
**/
fillg0cmd(tpDsreq, CMDBUF(tpDsreq), READ, b1, 0, 0, b4, 0);
filldsreq (tpDsreq, tpBlock, TAPE_BLOCK_SIZE, DSRQ_READ | DSRQ_SENSE);
old_timer = TIME(tpDsreq);
TIME(tpDsreq) = TIMER_READ_WRITE;
err = issueCmd();
TIME(tpDsreq) = old_timer;
return ( err );
}
/*************************************************************************
* w r i t e F m
*************************************************************************
*
* Name: writeFm
*
* Purpose: Writes File Mark at current position.
*
* Returns: 0 = Success, -1 = Error
*
*************************************************************************/
writeFm()
{
uchar_t b4;
int err;
if ( tpStat.st_state & TAPE_WRITE_PROTECT ) {
printf ("dat> Tape is write protected.");
return(-1);
}
b4 = 0x01; /* we always write one File Mark */
printf ("dat>\t ...Writing End_of_file_mark\n");
fillg0cmd(tpDsreq, CMDBUF(tpDsreq), WRITE_FM, 0, 0, 0, b4, 0);
filldsreq (tpDsreq, 0, 0, DSRQ_WRITE | DSRQ_SENSE);
err = issueCmd();
tpStat.st_fno++;
tpStat.st_state |= TAPE_WRITTEN;
return ( err );
}
/*************************************************************************
* r e w i n d T a p e
*************************************************************************
*
* Name: rewindTape
*
* Purpose: Rewind the tape.
*
* Returns: 0 = Success, -1 = Error
*
*************************************************************************/
rewindTape()
{
register int err;
ulong old_timer;
printf ("dat>\t ...Rewinding the tape.\n");
/* Create a Rewind Cdb buffer and issue the command */
fillg0cmd (tpDsreq, CMDBUF(tpDsreq), REWIND, 0, 0 , 0, 0, 0 );
filldsreq (tpDsreq, 0, 0, DSRQ_WRITE | DSRQ_SENSE);
old_timer = TIME(tpDsreq);
TIME(tpDsreq) = TIMER_REWIND;
err = issueCmd();
TIME(tpDsreq) = old_timer;
tpStat.st_fno = 0;
return ( err );
}
/*************************************************************************
* l o a d T a p e
*************************************************************************
*
* Name: loadTape
*
* Purpose: Loads the tape and changes tape block size to our
* own 1024 byte size (default is 512).
*
* Returns: 0 = Success, -1 = Failure
*
*************************************************************************/
loadTape()
{
uchar_t b1, b4;
uchar_t *p;
printf ("dat>\t ...Loading the tape.\n");
fillg0cmd (tpDsreq, CMDBUF(tpDsreq), LOAD_UNLOAD, 0, 0, 0, LOAD_BIT, 0);
filldsreq (tpDsreq, 0, 0, DSRQ_READ | DSRQ_SENSE);
if ( issueCmd() == -1 )
return (-1);
/*
* Issue a Mode_Select and set the tape block size to ours.
*/
/* set Parameter list for Mode_Select */
p = tpBlock;
p[0] = p[1] = p[2] = 0;
p[2] |= 0x10;
p[3] = 8;
/* set Block Descriptor fields */
p += 4;
p[0] = p[1] = p[2] = p[3] = p[4] = 0;
p[5] = TAPE_BLOCK_SIZE >> 16; /* MSB of block size */
p[6] = TAPE_BLOCK_SIZE >> 8; /* block size */
p[7] = TAPE_BLOCK_SIZE; /* LSB of block size */
b1 = 0;
b1 |= PF_BIT; /* Scsi-2 format */
b4 = 12; /* 4 for Param. List header, 8 for Block Descriptor */
fillg0cmd (tpDsreq, CMDBUF(tpDsreq), MODE_SELECT, b1, 0, 0, b4, 0);
filldsreq (tpDsreq, tpBlock, TAPE_BLOCK_SIZE, DSRQ_WRITE | DSRQ_SENSE);
if ( issueCmd() == -1 )
return (-1);
tpStat.st_state |= TAPE_LOADED;
tpStat.st_state &= ~TAPE_WRITTEN;
tpStat.st_part = DIR_PARTITION;
return(0);
}
/*************************************************************************
* u n l o a d T a p e
*************************************************************************
*
* Name: unloadTape
*
* Purpose: Unloads the tape.
*
* Returns: 0 = Success, -1 = Failure
*
*************************************************************************/
unloadTape()
{
ulong old_timer;
short err;
printf ("dat>\t ...Unloading the tape\n");
fillg0cmd (tpDsreq, CMDBUF(tpDsreq), LOAD_UNLOAD, 0, 0, 0,
UNLOAD_BIT, 0);
filldsreq (tpDsreq, 0, 0, DSRQ_READ | DSRQ_SENSE);
old_timer = TIME(tpDsreq);
TIME(tpDsreq) = TIMER_READ_WRITE;
err = issueCmd();
TIME(tpDsreq) = old_timer;
return ( err ) ;
}
/*************************************************************************
* i s s u e C m d
*************************************************************************
*
* Name: issueCmd
*
* Purpose: Issues a previously set up (in tpDsreq) command
* Retries for Busy condition and so on.
*
* Returns: 0 = Successful, -1 = Error
*
*************************************************************************/
issueCmd()
{
register int fd, issue_more, err;
uchar_t stat, ret;
issue_more = MAX_BUSY_RETRY;
fd = getfd(tpDsreq);
while ( issue_more ) {
errno = 0;
if ( doscsireq ( fd, tpDsreq ) == -1 ) {
printf ("dat> Error ..RET(dsreq) = %x, errno = %d\n",
RET(tpDsreq), errno );
return (-1);
}
/* check Target's returned status */
stat = STATUS(tpDsreq);
/* printf ("dat> STATUS(dsreq) = %x, RET(dsreq) = %x\n", stat, ret); */
/* see what happened */
switch ( stat ) {
/* good */
case STA_GOOD:
return (0);
/* darn thing is busy - try again */
case STA_BUSY:
printf ("dat>\t ...Tape busy - trying again...\n");
issue_more--;
sleep(1);
break;
/* something went wrong */
case STA_CHECK:
if ( checkSense() == 0 ) {
return(0);
}
return (-1);
}
}
return(-1);
}
/*************************************************************************
* c h e c k S e n s e
*************************************************************************
*
* Name: checkSense
*
* Purpose: A Unit Check has occured. We check the sense byte 12,13
* and try to come up with a good error description
*
* Returns: 0 = Try again, else 1 for error.
*
*************************************************************************/
checkSense()
{
uchar_t senseKey;
uchar_t b15;
ushort_t b1213;
caddr_t sbuf;
sbuf = SENSEBUF(tpDsreq);
b15 = sbuf[15];
senseKey = sbuf[2] & 0x0F;
b1213 = (sbuf[12] << 8) | (sbuf[13]);
printf ("dat>\t ...Sense key: %x, b12,13 = %x, %x\n",
senseKey, sbuf[12], sbuf[13] );
/* if no Sense Key, then everything is ok */
if (senseKey == 0 ) {
if ( b1213 == 0x0001)
printf ("dat>\t ...SENSE: File_Mark Detected\n");
if ( b1213 == 0x0002 )
printf ("dat>\t ...ERROR: End of Partition\n");
if ( b1213 == 0x0003)
printf ("dat>\t ...ERROR: Setmark Detected\n");
if ( b1213 == 0x0004)
printf ("dat>\t ...ERROR: Beginning of Partition\n");
return(-1);
}
/* Write protected tape */
if ( senseKey == 0x07 ) {
printf ("dat>\t ...SENSE: Tape is Write Protected\n");
tpStat.st_state |= TAPE_WRITE_PROTECT;
return(0);
}
/* In process of becoming ready */
if ( (senseKey == 0x02) && (b1213 == 0x0401) ) {
printf ("dat>\t ...SENSE: Tape is becoming Ready\n");
return(0);
}
/* Not ready to ready transition in progress */
if ( (senseKey == 0x06) && (b1213 == 0x2800) ) {
printf ("dat>\t ...SENSE: Not_Reay to Ready transition\n");
return(0);
}
/* Write Protect tape */
if ( (senseKey == 0x07) && (b1213 == 0x2700) ) {
printf ("dat>\t ...SENSE: Tape is Write Protected\n");
tpStat.st_state |= TAPE_WRITE_PROTECT;
return(0);
}
/* End of Tape or Partition */
if ( (senseKey == 0x0D) && (b1213 = 0x0002) ) {
printf ("dat>\t ...SENSE: End of Tape or Partition\n");
return(-1);
}
/* No Cassette is present */
if ( (senseKey == 0x02) && (b1213 == 0x3a00) ) {
printf ("dat>\t ...SENSE: No Cassette in the drive\n");
return(-1);
}
return(-1);
}
/*************************************************************************
* s h o w H e l p
*************************************************************************
*
* Name: showHelp
*
* Purpose: Show supported commands.
*
* Returns: None.
*
*************************************************************************/
showHelp()
{
printf (" Available commands are: \n\n");
printf (" init: Initialize the tape\n");
printf (" open: Open the tape\n");
printf (" dir: Show directory of the Tape\n");
printf (" diru: Directory Up\n");
printf (" dird: Directory Down\n");
printf (" read: Read a file from Tape to Disk\n");
printf (" write: Write a file from Disk to Tape\n");
printf (" close: Close the tape\n");
printf (" exit: Exit the program\n");
}
/*************************************************************************
* s h o w D i r
*************************************************************************
*
* Name: showDir
*
* Purpose: Shows the Directory of the Tape
*
* Returns: None
*
*************************************************************************/
showDir( int sno )
{
char *prepDate();
register int i, row;
tpDir_t *tpd;
if ( !(tpStat.st_state & TAPE_LOADED) ) {
printf ("dat> No tape is opened yet\n");
return;
}
if ( tpCb.cb_dtot == 0 ) {
printf ("dat> Tape is empty\n");
return;
}
/* adjust starting file number */
if ( sno >= tpCb.cb_dtot)
sno -= SCREEN_SIZE;
if ( sno < 0 )
sno = 0;
/* there is something to show */
printf ("%-3s %-20s %-6s %-25s %-5s\n",
"No", "Name", "Length", "Date/Time", "Type");
for ( row = 0; row < SCREEN_SIZE; row++, sno++ ) {
if ( sno >= tpCb.cb_dtot )
break;
tpd = &tpDir[sno];
printf ("%-3d %-20s %-6d %-25s %-5s\n",
tpd->d_fno,
tpd->d_name,
tpd->d_len,
prepDate(&tpd->d_time),
(tpd->d_type == TYPE_FILE ? "File":"Unknown") );
}
printf ("\nTotal files: %d Current position: %d\n\n",
tpCb.cb_dtot, tpStat.st_fno );
tpStat.st_dno = sno;
}
/*************************************************************************
* p r e p D a t e
*************************************************************************
*
* Name: prepDate
*
* Purpose: formats Date/Time (returns whatever ctime() returns
* minues the CR at the end).
*
* Returns: Pointer to the dat format
*
*************************************************************************/
char *
prepDate( long *d)
{
register int i;
register char *s;
s = ctime (d);
for ( i = 0; s[i] != '\n'; i++ );
s[i] = '\0';
return (s);
}
/*************************************************************************
* c l e a n U p
*************************************************************************
*
* Name: cleanUp
*
* Purpose: Unloads the tape if necessary and exits the program
*
* Returns: None
*
*************************************************************************/
cleanUp()
{
if ( tpStat.st_state & TAPE_LOADED )
unloadTape();
dsclose(tpDsreq);
printf ("dat> Dsreq connection closed\n");
exit(0);
}